﻿#include "sbdefs.h"

#if _SB_WINDOWS_UNIVERSAL_
#include "pch.h"
#include "sbcsp.h"
#include "sbctn.h"

// 이거 정말 동작하는지 테스트해봐야 함
#include "WindowsThreadEmulation.h"

#ifndef TLS_OUT_OF_INDEXES
#define TLS_OUT_OF_INDEXES	0xFFFFFFFF
#endif

//////////////////////////////////////////////////////////////////////////
// 스레드 정의
struct kThread
{
	kcham				canwait;
	kint				busytype;
	kint				stacksize;

	kanycb				cb;
	kpointer			ret;

	HANDLE				handle;
	DWORD				thdid;

	HANDLE				cond;
	kpointer			tls[64];

	kcham				isreg;
	kThread*			next;
};


//////////////////////////////////////////////////////////////////////////
// 스레드 구현
static struct kImplThd
{
	bool				init;
	DWORD				selftls;
	kuint				tlsindex;
	kNotifyFunc			tlsdispose[64];

	kThread*			selfthd;
	kThread*			thdlist;
} k_impl_thd =
{
	false,
	TLS_OUT_OF_INDEXES,
	0,
	NULL,
};

static void _k_thd_finalize(kThread* self, kuint tlscnt, kcham force);

// 스레드 초기화
K_EXTERN_C void _k_thd_init(void)
{
	// 이거 더미. 실제로는 _k_thd_test_init()에서 함
}

// 스레드 제거
K_EXTERN_C void _k_thd_disp(void)
{
	if (k_impl_thd.init)
	{
		kThread* node;
		kThread* next;

		if (k_impl_thd.selftls != TLS_OUT_OF_INDEXES)
		{
			ThreadEmulation::TlsFree(k_impl_thd.selftls);
			k_impl_thd.selftls = TLS_OUT_OF_INDEXES;
		}

		for (node = k_impl_thd.thdlist; node; node = next)
		{
			next = node->next;
			_k_thd_finalize(node, k_impl_thd.tlsindex, TRUE);
		}
	}
}

// 진짜 초기화
void _k_thd_test_init(void)
{
	if (k_impl_thd.init)
		return;

	k_impl_thd.init = true;

	k_impl_thd.selftls = ThreadEmulation::TlsAlloc();

	k_impl_thd.selfthd = k_thread_self();
}

//
static kcham _k_thd_set_busy(kThread* self, kint busytype)
{
	HANDLE h = self->handle;
	kcham ret;

	switch (busytype)
	{
		case -2:
			ret = ThreadEmulation::SetThreadPriority(h, THREAD_PRIORITY_IDLE);
			break;

		case -1:
			ret = ThreadEmulation::SetThreadPriority(h, THREAD_PRIORITY_BELOW_NORMAL);
			break;

		case 0:
			ret = ThreadEmulation::SetThreadPriority(h, THREAD_PRIORITY_NORMAL);
			break;

		case 1:
			ret = ThreadEmulation::SetThreadPriority(h, THREAD_PRIORITY_ABOVE_NORMAL);
			break;

		case 2:
			ret = ThreadEmulation::SetThreadPriority(h, THREAD_PRIORITY_HIGHEST);
			break;

		default:
			ret = FALSE;
			break;
	}

	return ret;
}

//
static void _k_thd_finalize(kThread* self, kuint tlscnt, kcham force)
{
	kuint i;

	for (i = 0; i < tlscnt; i++)
	{
		kNotifyFunc func = k_impl_thd.tlsdispose[i];
		kpointer value = self->tls[i];

		if (func && value)
			func(value);
	}

	if (self->cond)
	{
		CloseHandle(self->cond);
		self->cond = NULL;
	}

	if (self->canwait)
	{
		if (force)
			k_delete(self);
	}
	else
	{
		if (self->handle)
		{
			CloseHandle(self->handle);
			self->handle = NULL;
		}

		// 대기 안하는 애들은 강제 지움
		k_delete(self);
	}
}

//
static void _k_thd_exit(kThread* self, kcham isexit)
{
	kThread* node;
	kThread* prev;
	int tlscnt;

	if (self->isreg)
	{
		self->isreg = FALSE;
		kp_lock();

		for (node = k_impl_thd.thdlist, prev = NULL; node; prev = node, node = node->next)
		{
			if (node == self)
			{
				if (prev)
					prev->next = node->next;
				else
					k_impl_thd.thdlist = node->next;

				break;
			}
		}

		kp_unlock();
	}

	tlscnt = k_impl_thd.tlsindex;
	_k_thd_finalize(self, tlscnt, FALSE);

	ThreadEmulation::TlsSetValue(k_impl_thd.selftls, NULL);

	if (isexit)
	{
		//ExitThread(0);
		k_tracef(KLV_CRITICAL, "thread", "cannot exit thread on Windows Store APP");
	}
}

//
static HANDLE _k_thd_cond(void)
{
	kThread* self = k_thread_self();

	if (!self->cond)
	{
		self->cond = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);

		if (self->cond == NULL)
			k_traces(KLV_FATAL, "k.cond", NULL);
	}

	return self->cond;
}


//////////////////////////////////////////////////////////////////////////
// tls

// 만들기
kTls* k_tls(kNotifyFunc func)
{
	kTls* self;

	_k_thd_test_init();

	kp_lock();

	if (k_impl_thd.tlsindex >= 64)
	{
		kp_unlock();

		k_tracef(KLV_FATAL, "k.tls", "too many tls used. limited value is %d.", 64);
		return NULL;
	}

	self = (kTls*)K_CAST_UINT_TO_PTR(k_impl_thd.tlsindex + 0x10);
	k_impl_thd.tlsdispose[k_impl_thd.tlsindex] = func;
	k_impl_thd.tlsindex++;

	kp_unlock();

	return self;
}

// 넣기
void k_settls(kTls* self, kpointer value)
{
	kThread* thd;
	kuint at = K_CAST_PTR_TO_UINT(self) - 0x10;

	k_return_if_fail(at < 64);

	thd = k_thread_self();
	thd->tls[at] = value;
}

// 얻기
kpointer k_gettls(kTls* self)
{
	kThread* thd = k_thread_try();
	kuint at = K_CAST_PTR_TO_UINT(self) - 0x10;

	return (at >= 64 || !thd) ? NULL : thd->tls[at];
}


//////////////////////////////////////////////////////////////////////////
// 스레드

kThread* k_thread_try(void)
{
	_k_thd_test_init();

	kThread* self = (kThread*)ThreadEmulation::TlsGetValue(k_impl_thd.selftls);
	return self;
}

kThread* k_thread_self(void)
{
	_k_thd_test_init();

	kThread* self;

	self = (kThread*)ThreadEmulation::TlsGetValue(k_impl_thd.selftls);

	if (!self)
	{
		HANDLE h, p, d;

		self = k_new_1(kThread);

		h = GetCurrentThread();
		p = GetCurrentProcess();

		if (!DuplicateHandle(p, h, p, &d, 0, FALSE, DUPLICATE_SAME_ACCESS))
			k_traces(KLV_FATAL, "k.self", NULL);

		self->thdid = GetCurrentThreadId();
		self->handle = d;

		self->canwait = FALSE;
		self->busytype = 0;
		self->stacksize = 0;

		self->cb.func = NULL;
		self->cb.data = NULL;

		self->cond = NULL;
		k_zero(self->tls, 64, kpointer);

		self->isreg = TRUE;

		kp_lock();
		self->next = k_impl_thd.thdlist;
		k_impl_thd.thdlist = self;
		kp_unlock();

		ThreadEmulation::TlsSetValue(k_impl_thd.selftls, self);
	}

	return self;
}

// 만들기
kThread* k_thread_new(kThreadFunc func, kpointer userdata, kuint stacksize, kuint busytype)
{
	_k_thd_test_init();

	kThread* self = k_new_1(kThread);

	self->canwait = TRUE;
	self->busytype = busytype;
	self->stacksize = stacksize;
	self->cb.func = (kfunc)func;
	self->cb.data = userdata;

	self->ret = NULL;
	self->handle = NULL;
	self->thdid = 0;
	self->cond = NULL;
	k_zero(self->tls, 64, kpointer);

	self->isreg = TRUE;

	kp_lock();
	self->next = k_impl_thd.thdlist;
	k_impl_thd.thdlist = self;
	kp_unlock();

	return self;
}

// 만들기
kcham k_thread_run(kThreadFunc func, kpointer userdata, kuint stacksize, kuint busytype)
{
	_k_thd_test_init();

	kThread* self = k_new_1(kThread);

	self->canwait = FALSE;
	self->busytype = busytype;
	self->stacksize = stacksize;
	self->cb.func = (kfunc)func;
	self->cb.data = userdata;

	self->ret = NULL;
	self->handle = NULL;
	self->thdid = 0;
	self->cond = NULL;
	k_zero(self->tls, 64, kpointer);

	self->isreg = TRUE;

	kp_lock();
	self->next = k_impl_thd.thdlist;
	k_impl_thd.thdlist = self;
	kp_unlock();

	return k_thread_start(self);
}

// 제거
void k_thread_delete(kThread* self)
{
	_k_thd_test_init();

	kThread* node;
	kThread* prev;

	k_return_if_fail(self->canwait != FALSE);

	k_thread_wait(self);

	if (self->isreg)
	{
		self->isreg = FALSE;
		kp_lock();

		for (node = k_impl_thd.thdlist, prev = NULL; node; prev = node, node = node->next)
		{
			if (node == self)
			{
				if (prev)
					prev->next = node->next;
				else
					k_impl_thd.thdlist = node->next;

				break;
			}
		}

		kp_unlock();
	}

	k_delete(self);
}

// 스레드 시작 지점
static DWORD WINAPI _k_thd_entry(kpointer ptr)
{
	kThread* self = (kThread*)ptr;

	ThreadEmulation::TlsSetValue(k_impl_thd.selftls, self);

	kp_lock();
	kp_unlock();

	self->ret = ((kThreadFunc)self->cb.func)(self->cb.data);

	_k_thd_exit(self, TRUE);

	return 0;
}

// 시작
kcham k_thread_start(kThread* self)
{
	_k_thd_test_init();

	k_return_value_if_fail(self->handle == NULL, FALSE);

	kp_lock();

	self->handle = ThreadEmulation::CreateThread(NULL, self->stacksize, &_k_thd_entry, self, 0, &self->thdid);

	if (self->handle == NULL || self->handle == INVALID_HANDLE_VALUE)
	{
		kp_unlock();

		k_traces(KLV_CRITICAL, "k.thread", NULL);
		return FALSE;
	}

	_k_thd_set_busy(self, self->busytype);

	kp_unlock();

	return TRUE;
}

// 기둘
kpointer k_thread_wait(kThread* self)
{
	_k_thd_test_init();

	kpointer ret;
	DWORD dw;

	k_return_value_if_fail(self->canwait != FALSE, NULL);

	if (self->handle == NULL)
		ret = NULL;
	else
	{
		dw = WaitForSingleObjectEx(self->handle, INFINITE, FALSE);

		if (dw != WAIT_OBJECT_0)
			k_traces(KLV_ERROR, "k.thread.wait", NULL);

		CloseHandle(self->handle);
		self->handle = NULL;

		ret = self->ret;
	}

	return ret;
}

// 나가기
void k_thread_exit(kpointer ret)
{
	_k_thd_test_init();

	kThread* self = k_thread_self();

	self->ret = ret;

	_k_thd_exit(self, TRUE);
}

// 바쁨
kcham k_thread_set_busy(kThread* self, kint busytype)
{
	_k_thd_test_init();

	if (self->handle == NULL)
	{
		self->busytype = busytype;
		return TRUE;
	}
	else
	{
		if (!_k_thd_set_busy(self, busytype))
			return FALSE;
		else
		{
			self->busytype = busytype;
			return TRUE;
		}
	}
}

// 스택 크기
kcham k_thread_set_stack(kThread* self, kuint stacksize)
{
	if (self->handle != NULL)
		return FALSE;
	else
	{
		self->stacksize = stacksize;
		return TRUE;
	}
}

// 콜백
kcham k_thread_set_cb(kThread* self, kThreadFunc func, kpointer data)
{
	if (self->handle != NULL)
		return FALSE;
	else
	{
		self->cb.func = (kfunc)func;
		self->cb.data = data;
		return TRUE;
	}
}

// 우선 순위
kint k_thread_get_busy(kThread* self)
{
	return self->busytype;
}

// 스택
kuint k_thread_get_stack(kThread* self)
{
	return self->stacksize;
}

// 기둘
kcham k_thread_can_wait(kThread* self)
{
	return self->canwait;
}

// 아이디
kuint k_thread_get_id(kThread* self)
{
	return self->thdid;
}


//////////////////////////////////////////////////////////////////////////
// 뮤텍스
struct kMutex
{
	CRITICAL_SECTION    cs;
};

kMutex* k_mutex_new(void)
{
	kMutex* self = k_new_1(kMutex);
	InitializeCriticalSectionEx(&self->cs, 0, 0);
	return self;
}

kMutex* k_mutex_new_count(kint count)
{
	kMutex* self = k_new_1(kMutex);

	InitializeCriticalSectionEx(&self->cs, (DWORD)count, 0);

	return self;
}

void k_mutex_delete(kMutex* self)
{
	DeleteCriticalSection(&self->cs);
	k_delete(self);
}

void k_mutex_try(kMutex* self)
{
	TryEnterCriticalSection(&self->cs);
}

void k_mutex_enter(kMutex* self)
{
	EnterCriticalSection(&self->cs);
}

void k_mutex_leave(kMutex* self)
{
	LeaveCriticalSection(&self->cs);
}


//////////////////////////////////////////////////////////////////////////
// 컨디션
struct kCond
{
	kPtrArr				evs;
	CRITICAL_SECTION	cs;
};

kCond* k_cond_new(void)
{
	kCond* self = k_new_1(kCond);

	k_parr_init(&self->evs, 0);
	InitializeCriticalSectionEx(&self->cs, 0, 0);

	return self;
}

void k_cond_delete(kCond* self)
{
	DeleteCriticalSection(&self->cs);
	k_parr_disp(&self->evs);
	k_delete(self);
}

void k_cond_signal(kCond* self)
{
	EnterCriticalSection(&self->cs);

	if (k_parr_is_have(&self->evs))
	{
		SetEvent((HANDLE)k_parr_nth(&self->evs, 0));
		k_parr_remove_nth(&self->evs, 0);
	}

	LeaveCriticalSection(&self->cs);
}

void k_cond_broadcast(kCond* self)
{
	kssize_t i;

	EnterCriticalSection(&self->cs);

	for (i = 0; i < k_parr_count(&self->evs); i++)
		SetEvent((HANDLE)k_parr_nth(&self->evs, i));

	k_parr_clear(&self->evs);

	LeaveCriticalSection(&self->cs);
}

kcham k_cond_wait_for(kCond* self, kMutex* enterlock, kint milliseconds)
{
	HANDLE ev;
	DWORD dw;

	k_return_value_if_fail(enterlock != NULL, FALSE);

#if 0
	// 끝 시간 지정 방식일 때 구현해본거 -> pthread 호환을 위해
	if (!endtime)
		milliseconds = INFINITE;
	else
	{
		kTimeVal tv;
		k_timeval_now(&tv);

		if (endtime->tv_sec < tv.tv_sec || (endtime->tv_sec == tv.tv_sec && endtime->tv_usec <= tv.tv_usec))
			milliseconds = 0;
		else
			milliseconds = (endtime->tv_sec - tv.tv_sec) * 1000 + (endtime->tv_usec - tv.tv_usec) / 1000;
	}
#endif

	ev = _k_thd_cond();

	EnterCriticalSection(&self->cs);

	dw = WaitForSingleObjectEx(ev, 0, FALSE);

	if (dw != WAIT_TIMEOUT)
		k_traces(KLV_ERROR, "k.cond", "event is already signaled!");

	k_parr_add(&self->evs, ev);
	LeaveCriticalSection(&self->cs);

	k_mutex_leave(enterlock);

	dw = WaitForSingleObjectEx(ev, milliseconds < 0 ? INFINITE : milliseconds, FALSE);

	if (dw == WAIT_FAILED)
		k_traces(KLV_ERROR, "k.cond", NULL);

	k_mutex_enter(enterlock);

	if (dw == WAIT_TIMEOUT)
	{
		EnterCriticalSection(&self->cs);
		k_parr_remove(&self->evs, ev);

		if (dw == WAIT_FAILED)
			k_traces(KLV_ERROR, "k.cond", NULL);

		LeaveCriticalSection(&self->cs);
	}

	return dw != WAIT_TIMEOUT;
}

void k_cond_wait(kCond* self, kMutex* enterlock)
{
	k_cond_wait_for(self, enterlock, -1);
}


//////////////////////////////////////////////////////////////////////////
// 세마포어
struct kSem
{
	HANDLE				h;
	kint				c;
};

kSem* k_sem_new(kint initial)
{
	kSem* self = k_new_1(kSem);

	self->h = CreateSemaphoreEx(NULL, initial, 32 * 1024, NULL, 0, SEMAPHORE_ALL_ACCESS);
	self->c = initial;

	if (!self->h)
	{
		k_traces(KLV_ERROR, "k.sem", "fail to create semaophore");
		k_delete(self);
		self = NULL;
	}

	return self;
}

void k_sem_delete(kSem* self)
{
	CloseHandle(self->h);

	k_delete(self);
}

kcham k_sem_wait_for(kSem* self, kint milliseconds)
{
	DWORD dw;

	dw = WaitForSingleObjectEx(self->h, milliseconds < 0 ? INFINITE : milliseconds, FALSE);

	if (dw != WAIT_OBJECT_0)
	{
		if (dw == WAIT_FAILED)
			k_traces(KLV_ERROR, "k.sem.wait", NULL);

		return FALSE;
	}
	else
	{
		k_atomdec(&self->c);

		return TRUE;
	}
}

kcham k_sem_wait(kSem* self)
{
	return k_sem_wait_for(self, -1);
}

kcham k_sem_try_wait(kSem* self)
{
	return k_sem_wait_for(self, 0);
}

kint k_sem_value(kSem* self)
{
	return self->c;
}

kcham k_sem_post(kSem* self)
{
	k_atominc(&self->c);

	if (ReleaseSemaphore(self->h, 1, NULL))
		return TRUE;
	else
	{
		k_atomdec(&self->c);
		k_traces(KLV_ERROR, "k.sem.post", NULL);

		return FALSE;
	}
}

#endif
